package tpc2;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.SocketException;
import java.util.ArrayList;

import commsCPN.JavaCPN;

public class CollectVotes {
    public enum Responses {
        COMMIT, ABORT;
        public String toString() {
            return this.name().toLowerCase();
        }
    };

    private static ArrayList<Vote> votes;
    private static JavaCPN jCPN = null;
    private static Integer numOfWorkers = null;
    private static String response = Responses.COMMIT.toString();

    public CollectVotes(JavaCPN jCPN, int numOfWorkers) {
        this.jCPN = jCPN;
        this.numOfWorkers = numOfWorkers;
        votes = new ArrayList<Vote>();
    }

    /**
     * Can be used for testing the CollectVotes module alone with the CPN-Model.
     * 
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        jCPN = new JavaCPN();
        votes = new ArrayList<Vote>();

        while (true) {
            System.out.println("Status: listening");
            jCPN.accept(9000);
            ArrayList<Integer> waitingAcknowledgements = run();
            StringBuilder sb = new StringBuilder();
            sb.append("AwaitingAcks:");
            for (Integer i : waitingAcknowledgements) {
                sb.append(i.toString() + ","); // "AwaitingAcks:workerId,workerId,workerId...."
            }
            jCPN.send(new ByteArrayInputStream(sb.toString().getBytes()));
            jCPN.send(new ByteArrayInputStream("Reset:".getBytes()));
            jCPN.disconnect();
            System.out.println("Status: completed");
        }
    }

    /**
     * Start receiving data from the CPN-Model using the assigned JavaCPN instance.
     * Returns the number of expected acknowledgments on success, -1 on error.
     * @return
     * @throws SocketException
     */
    public static ArrayList<Integer> run() throws SocketException {
        String receivedDataStr = null;
        String[] rData;

        while (true) {
            try {
                receivedDataStr = jCPN.receive().toString();
                System.out.println("Received: " + receivedDataStr);
            } catch (java.net.SocketException e) {
                resetState();
                break;
            }

            rData = receivedDataStr.split(":");
            if (rData[0].equals("numOfWorkers")) {
                setNumberOfWorkers(rData[rData.length - 1]);
            }
            if (rData[0].equals("Vote")) {
                addVote(rData[1]);
            }
            ArrayList<Integer> waitingAcknowledgements = sendDecisions();
            if (waitingAcknowledgements != null) {
                return waitingAcknowledgements;
            }
        }
        return null;
    }

    /**
     * Resets the list of decisions and sets the number of workers.
     * 
     * @param numOfWorkers
     */
    public static void setNumberOfWorkers(String numOfWorkers) {
        resetState();
        CollectVotes.numOfWorkers = new Integer(Integer.parseInt(numOfWorkers));
    }

    /**
     * Evaluates the received votes and makes the decision to commit or abort
     * the transaction. if all workers vote yes -> send "commit" for all
     * workers. if some or all workers vote no -> send "abort" to those that
     * voted yes (those that voted "no" are discarded)
     * 
     * at successful evaluation
     * a list of worker id's (who's acknowledgment is expected) are returned
     * else null
     * 
     * @throws SocketException
     * @throws NumberOfWorkersException
     * @return ArrayList<Integer>
     */
    private static ArrayList<Integer> sendDecisions() throws SocketException {
        ArrayList<Integer> waitingAcknowledgements = new ArrayList<Integer>();
        if (votes.size() == numOfWorkers) {
            StringBuilder sb = new StringBuilder();
            sb.append("Decision:");
            for (Vote v : votes) {
                if (v.vote == Vote.Response.No) {
                    response = Responses.ABORT.toString();
                    break;
                }
            }

            for (Vote v : votes) {
                if (v.vote == Vote.Response.Yes) {
                    waitingAcknowledgements.add(v.workerId);
                    sb.append(v.workerId).append(',').append(response).append(';');
                }
            }
            System.out.println("Sending: " + sb.toString());
            jCPN.send(new ByteArrayInputStream(sb.toString().getBytes()));
            resetState();
            return waitingAcknowledgements;
        }
        return null;
    }

    /**
     * Parses and adds the vote to the local list of votes.
     * 
     * @param decision
     */
    private static void addVote(String decision) {
        String rData[] = decision.split(",");
        Vote v = new Vote(rData[0].replaceAll("[^0-9]", ""), rData[1]);
        votes.add(v);
    }

    /**
     * Sets state to the initial state (JavaCPN instance is NOT renewed)
     * response is set to commit, Number of workers to null, clears votes.
     */
    private static void resetState() {
        response = Responses.COMMIT.toString();
        numOfWorkers = null;
        votes.clear();
    }
}
